package org.example.ai04.util;

import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.crypto.SecretKey;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * JWT工具类
 * 提供JWT（JSON Web Token）令牌的生成、解析和验证功能
 * 使用io.jsonwebtoken库实现JWT标准，支持HMAC-SHA256签名算法
 * 在系统中主要用于用户身份认证和授权管理
 */
@Component
public class JwtUtil {

    // JWT密钥，从配置文件读取，默认为一个长字符串
    // 用于HMAC-SHA256算法的签名和验证
    @Value("${jwt.secret:mySecretKey123456789012345678901234567890}")
    private String secret;

    // JWT令牌过期时间，从配置文件读取，默认为24小时（86400000毫秒）
    // 单位为毫秒，控制令牌的有效时长
    @Value("${jwt.expiration:86400000}") // 24小时
    private Long expiration;

    /**
     * 获取用于签名JWT的密钥对象
     * 将配置的字符串密钥转换为SecretKey对象
     * 内部使用Keys.hmacShaKeyFor方法处理密钥材料
     * 
     * @return 用于JWT签名和验证的SecretKey对象
     */
    private SecretKey getSigningKey() {
        // 使用HMAC-SHA算法，将字符串密钥转换为SecretKey对象
        return Keys.hmacShaKeyFor(secret.getBytes());
    }

    /**
     * 从JWT令牌中提取用户名
     * 用户名通常存储在令牌的subject字段中
     * 
     * @param token JWT令牌字符串
     * @return 令牌中存储的用户名
     * @throws JwtException 如果令牌无效或已损坏
     */
    public String extractUsername(String token) {
        // 从令牌中提取主题（subject），此处存储的是用户名
        return extractClaim(token, Claims::getSubject);
    }

    /**
     * 从JWT令牌中提取过期时间
     * 用于判断令牌是否已过期
     * 
     * @param token JWT令牌字符串
     * @return 令牌的过期时间Date对象
     * @throws JwtException 如果令牌无效或已损坏
     */
    public Date extractExpiration(String token) {
        // 从令牌中提取过期时间
        return extractClaim(token, Claims::getExpiration);
    }

    /**
     * 从JWT令牌中提取指定的声明信息
     * 支持泛型，可以提取不同类型的声明
     * 使用函数式编程方式提供灵活的声明解析机制
     * 
     * @param token JWT令牌字符串
     * @param claimsResolver 声明解析函数，用于从Claims对象中提取特定信息
     * @param <T> 返回值类型
     * @return 解析后的声明值
     * @throws JwtException 如果令牌无效或已损坏
     */
    public <T> T extractClaim(String token, java.util.function.Function<Claims, T> claimsResolver) {
        final Claims claims = extractAllClaims(token);
        // 应用提供的解析函数提取特定声明
        return claimsResolver.apply(claims);
    }

    /**
     * 从JWT令牌中提取所有声明信息
     * 验证令牌签名并解析令牌内容
     * 这是JWT解析的核心方法
     * 
     * @param token JWT令牌字符串
     * @return 令牌中的所有声明Claims对象
     * @throws JwtException 如果令牌无效、签名错误或已过期
     */
    private Claims extractAllClaims(String token) {
        try {
            // 创建JWT解析器，设置签名密钥
            return Jwts.parserBuilder()
                    .setSigningKey(getSigningKey())
                    .build()
                    // 解析JWT令牌，验证签名
                    .parseClaimsJws(token)
                    // 获取令牌中的声明主体
                    .getBody();
        } catch (Exception e) {
            System.out.println("解析token claims失败: " + e.getMessage());
            throw e;
        }
    }

    /**
     * 检查JWT令牌是否已过期
     * 通过比较令牌的过期时间与当前系统时间
     * 
     * @param token JWT令牌字符串
     * @return 如果令牌已过期返回true，否则返回false
     * @throws JwtException 如果令牌无效或已损坏
     */
    private Boolean isTokenExpired(String token) {
        // 比较令牌过期时间与当前时间
        return extractExpiration(token).before(new Date());
    }

    /**
     * 生成JWT令牌
     * 根据用户名和角色创建包含用户身份信息的令牌
     * 令牌包含用户角色作为自定义声明
     * 
     * @param username 用户名，将作为令牌的主题
     * @param role 用户角色，将作为令牌的自定义声明
     * @return 生成的JWT令牌字符串
     */
    public String generateToken(String username, String role) {
        // 创建令牌声明，存储用户角色信息
        Map<String, Object> claims = new HashMap<>();
        claims.put("role", role);
        // 调用createToken方法生成令牌
        return createToken(claims, username);
    }

    /**
     * 创建JWT令牌的核心方法
     * 设置令牌的各种属性并使用HMAC-SHA256算法签名
     * 包含声明信息、主题、创建时间和过期时间等标准JWT属性
     * 
     * @param claims 要包含在令牌中的自定义声明
     * @param subject 令牌的主题，通常是用户名
     * @return 生成的JWT令牌字符串（紧凑格式）
     */
    private String createToken(Map<String, Object> claims, String subject) {
        // 使用JWT构建器创建令牌
        return Jwts.builder()
                // 设置声明信息（包含自定义字段）
                .setClaims(claims)
                // 设置主题（用户名）
                .setSubject(subject)
                // 设置令牌创建时间
                .setIssuedAt(new Date(System.currentTimeMillis()))
                // 设置令牌过期时间
                .setExpiration(new Date(System.currentTimeMillis() + expiration))
                // 使用HMAC-SHA256算法和签名密钥签名
                .signWith(getSigningKey(), SignatureAlgorithm.HS256)
                // 压缩生成最终的令牌字符串
                .compact();
    }

    /**
     * 验证JWT令牌的有效性
     * 检查用户名是否匹配以及令牌是否过期
     * 这是一个完整的令牌验证方法，包括身份和时间有效性检查
     * 
     * @param token JWT令牌字符串
     * @param username 待验证的用户名
     * @return 如果令牌有效（用户名匹配且未过期）返回true，否则返回false
     * @throws JwtException 如果令牌结构无效或签名错误
     */
    public Boolean validateToken(String token, String username) {
        final String extractedUsername = extractUsername(token);
        // 验证用户名匹配且令牌未过期
        return (extractedUsername.equals(username) && !isTokenExpired(token));
    }

    /**
     * 仅验证JWT令牌的结构和签名是否有效
     * 不验证令牌中的用户名和过期时间，仅检查令牌格式和签名
     * 
     * @param token JWT令牌字符串
     * @return 如果令牌结构和签名有效返回true，否则返回false
     */
    public Boolean validateToken(String token) {
        try {
            // 尝试解析令牌，验证签名
            Jwts.parserBuilder()
                .setSigningKey(getSigningKey())
                .build()
                .parseClaimsJws(token);
            return true;
        } catch (JwtException | IllegalArgumentException e) {
            // 如果解析失败（无效令牌、过期等），返回false
            System.out.println("Token验证失败: " + e.getMessage());
            return false;
        }
    }

    /**
     * 从JWT令牌中提取用户角色信息
     * 角色信息存储在令牌的自定义声明中，键为"role"
     * 
     * @param token JWT令牌字符串
     * @return 用户角色字符串
     * @throws JwtException 如果令牌无效或已损坏
     * @throws ClassCastException 如果角色信息格式不正确
     */
    public String extractRole(String token) {
        // 提取所有声明，然后获取角色信息
        Claims claims = extractAllClaims(token);
        return (String) claims.get("role");
    }
}